package edu.cmu.cs211.bacon.kevin;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
import java.util.LinkedList;

/**
 * 
 * Represents and parses a single movie (or episode of a show) from the IMDB.  
 *
 */
public class Movie {
    private String title;
    private String episode;
    private int year;
    
    public static final int UNKNOWN_YEAR = -1;
    
    static private List<Pattern> moviePatterns = new LinkedList<Pattern>();
    static {
        moviePatterns.add(Pattern.compile("\"(.*\\S)\"\\s*\\((\\d\\d\\d\\d)\\).*(\\{.*\\}).*"));
        moviePatterns.add(Pattern.compile("(.*\\S)\\s*\\((\\d\\d\\d\\d)\\).*(\\{.*\\}).*"));
        moviePatterns.add(Pattern.compile("\"(.*\\S)\"\\s*\\((\\d\\d\\d\\d)\\).*"));
        moviePatterns.add(Pattern.compile("(.*\\S)\\s*\\((\\d\\d\\d\\d)\\).*"));
        moviePatterns.add(Pattern.compile("([^\t].*)"));
    }
    static private final int TITLE_GROUP = 1;
    static private final int YEAR_GROUP = 2;
    static private final int EPISODE_GROUP = 3;

    /** 
     * 
     * Constructs a movie.  Does not make a deep copy of the provided
     * title and episode.
     * 
     * @param title
     *      The title of the movie or show.
     *      
     * @param episode
     *      The episode (if any) of the show.
     *      
     * @param year
     *      The year of the movie or show.
     * 
     * @throws NullPointerException
     *      If title is null.
     */
    public Movie(String title, String episode, int year) {
        if (title == null)
            throw new NullPointerException("Movie title must be non-null");
        
        this.title = title;
        this.episode = episode;
        this.year = year;
    }

    /** 
     * 
     * Constructs a movie.  Does not make a deep copy of the provided
     * title.
     * 
     * @param title
     *      The title of the movie or show.
     *      
     * @param year
     *      The year of the movie or show.
     * 
     * @throws NullPointerException
     *      If title is null.
     */
    public Movie(String title, int year) {
        this(title, null, year);
    }
    
    /** 
     * 
     * Constructs a movie.  Does not make a deep copy of the provided
     * title.
     * 
     * @param title
     *      The title of the movie or show.
     * 
     * @throws NullPointerException
     *      If title is null.
     */
    public Movie(String title) {
        this(title, null, UNKNOWN_YEAR);
    }
    
    /**
     * 
     * @return The title of the movie or show.
     */
    public String getTitle() {
        return title;
    }
    
    /**
     * 
     * @return The episode of the movie or show, or null if none.
     */
    public String getEpisode() {
        return episode;
    }
    
    /**
     * 
     * @return The year of the movie or show, or Movie.UNKNOWN_YEAR if none.
     */
    public int getYear() {
        return year;
    }
    
    /**
     * Parses an IMDB-formatted movie string, if possible.  This function is not
     * robust, and small changes to the format might cause parse errors and 
     * exceptions.
     * 
     * @param s
     *      An IMDB-formatted movie string.
     *      
     * @return
     *      A movie representing the IMDB movie string, or null if the movie string
     *      is mal-formatted.
     *      
     * @throws NullPointerException
     *      If s is null.
     */
    public static Movie parseMovie(String s) {
        for (Pattern p : moviePatterns) {
            Matcher movieMatcher = p.matcher(s);
            if (movieMatcher.matches()) {
                String title = movieMatcher.group(TITLE_GROUP);
                assert (title != null);

                int year = UNKNOWN_YEAR;
                String episode = null;
                if (YEAR_GROUP <= movieMatcher.groupCount())
                    year = Integer.parseInt(movieMatcher.group(YEAR_GROUP));
                if (EPISODE_GROUP <= movieMatcher.groupCount())
                    episode = movieMatcher.group(EPISODE_GROUP);
                
                return new Movie(title, episode, year);
            }
        }
        
        return null;
    }
    
    public String toString() {
        StringBuffer sb = new StringBuffer(title);
        if (year != UNKNOWN_YEAR)
            sb.append(" (" + year + ")");
        if (episode != null)
            sb.append(" " + episode);
        return sb.toString();
    }
    
    public boolean equals(Object o) {
        if (!(o instanceof Movie))
            return false;
        
        Movie m = (Movie) o;
        if (m.episode == null && episode != null)
            return false;
        if (m.episode != null && episode == null)
            return false;
        if (m.episode == null && episode == null)
            return (title.equals(m.title) && year == m.year);
        
        return (title.equals(m.title)
                && episode.equals(m.episode)
                && year == m.year);
    }
    
    public int hashCode() {
        return toString().hashCode();
    }
}
